#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
泰坦尼克生存预测项目
目标：预测乘客是否在泰坦尼克号沉船事故中幸存
"""

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
from sklearn.preprocessing import LabelEncoder, StandardScaler
import warnings
warnings.filterwarnings('ignore')

# 设置中文字体和图表样式
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
sns.set_style("whitegrid")

class TitanicSurvivalPredictor:
    """泰坦尼克生存预测类"""
    
    def __init__(self):
        self.data = None
        self.X_train = None
        self.X_test = None
        self.y_train = None
        self.y_test = None
        self.model = None
        self.feature_importance = None
        
    def load_data(self):
        """加载数据"""
        print("=== 加载数据 ===")
        
        # 从网络加载数据（也可以使用本地文件）
        url = "https://web.stanford.edu/class/archive/cs/cs109/cs109.1166/stuff/titanic.csv"
        try:
            self.data = pd.read_csv(url)
            print(f"数据加载成功！形状: {self.data.shape}")
            print(f"列名: {list(self.data.columns)}")
        except:
            print("网络数据加载失败，使用备用数据源")
            # 这里可以添加备用数据加载逻辑
            return False
            
        return True
    
    def explore_data(self):
        """探索性数据分析"""
        print("\n=== 数据探索 ===")
        
        # 基本信息
        print("数据基本信息:")
        print(self.data.info())
        print("\n数据前5行:")
        print(self.data.head())
        
        # 缺失值统计
        print("\n缺失值统计:")
        missing_data = self.data.isnull().sum()
        print(missing_data[missing_data > 0])
        
        # 目标变量分布
        print("\n生存情况分布:")
        survival_counts = self.data['Survived'].value_counts()
        print(survival_counts)
        print(f"生存率: {survival_counts[1] / len(self.data):.2%}")
        
        return self.data
    
    def visualize_data(self):
        """数据可视化"""
        print("\n=== 数据可视化 ===")
        
        # 创建可视化图表
        fig, axes = plt.subplots(2, 3, figsize=(15, 10))
        
        # 1. 生存率
        sns.countplot(data=self.data, x='Survived', ax=axes[0, 0])
        axes[0, 0].set_title('生存情况分布')
        
        # 2. 性别与生存
        sns.countplot(data=self.data, x='Sex', hue='Survived', ax=axes[0, 1])
        axes[0, 1].set_title('性别与生存关系')
        
        # 3. 船舱等级与生存
        sns.countplot(data=self.data, x='Pclass', hue='Survived', ax=axes[0, 2])
        axes[0, 2].set_title('船舱等级与生存关系')
        
        # 4. 年龄分布
        self.data['Age'].hist(bins=30, ax=axes[1, 0])
        axes[1, 0].set_title('年龄分布')
        axes[1, 0].set_xlabel('年龄')
        
        # 5. 票价分布
        self.data['Fare'].hist(bins=30, ax=axes[1, 1])
        axes[1, 1].set_title('票价分布')
        axes[1, 1].set_xlabel('票价')
        
        # 6. 登船港口与生存
        sns.countplot(data=self.data, x='Embarked', hue='Survived', ax=axes[1, 2])
        axes[1, 2].set_title('登船港口与生存关系')
        
        plt.tight_layout()
        plt.savefig('titanic_eda.png', dpi=300, bbox_inches='tight')
        plt.show()
    
    def preprocess_data(self):
        """数据预处理"""
        print("\n=== 数据预处理 ===")
        
        # 创建数据副本
        df = self.data.copy()
        
        # 1. 处理缺失值
        # 年龄用中位数填充
        df['Age'].fillna(df['Age'].median(), inplace=True)
        # 登船港口用众数填充
        df['Embarked'].fillna(df['Embarked'].mode()[0], inplace=True)
        # 删除舱号缺失的行（只有少量缺失）
        df = df.dropna(subset=['Cabin'])
        
        # 2. 特征工程
        # 创建家庭大小特征
        df['FamilySize'] = df['SibSp'] + df['Parch'] + 1
        
        # 创建是否独自旅行特征
        df['IsAlone'] = 0
        df.loc[df['FamilySize'] == 1, 'IsAlone'] = 1
        
        # 提取头衔特征
        df['Title'] = df['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)
        
        # 3. 编码分类变量
        label_encoders = {}
        categorical_cols = ['Sex', 'Embarked', 'Title']
        
        for col in categorical_cols:
            le = LabelEncoder()
            df[col] = le.fit_transform(df[col].astype(str))
            label_encoders[col] = le
        
        # 4. 选择特征
        features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked', 
                   'FamilySize', 'IsAlone', 'Title']
        
        X = df[features]
        y = df['Survived']
        
        # 5. 数据分割
        self.X_train, self.X_test, self.y_train, self.y_test = train_test_split(
            X, y, test_size=0.2, random_state=42, stratify=y
        )
        
        print(f"训练集大小: {self.X_train.shape}")
        print(f"测试集大小: {self.X_test.shape}")
        
        return X, y
    
    def train_model(self):
        """训练模型"""
        print("\n=== 模型训练 ===")
        
        # 使用随机森林算法
        self.model = RandomForestClassifier(
            n_estimators=100,
            max_depth=10,
            min_samples_split=5,
            min_samples_leaf=2,
            random_state=42
        )
        
        self.model.fit(self.X_train, self.y_train)
        
        # 交叉验证
        cv_scores = cross_val_score(self.model, self.X_train, self.y_train, cv=5)
        print(f"交叉验证准确率: {cv_scores.mean():.3f} (+/- {cv_scores.std() * 2:.3f})")
        
        # 特征重要性
        self.feature_importance = pd.DataFrame({
            'feature': self.X_train.columns,
            'importance': self.model.feature_importances_
        }).sort_values('importance', ascending=False)
        
        print("\n特征重要性排序:")
        print(self.feature_importance)
        
        return self.model
    
    def evaluate_model(self):
        """模型评估"""
        print("\n=== 模型评估 ===")
        
        # 预测
        y_pred = self.model.predict(self.X_test)
        
        # 准确率
        accuracy = accuracy_score(self.y_test, y_pred)
        print(f"测试集准确率: {accuracy:.3f}")
        
        # 分类报告
        print("\n分类报告:")
        print(classification_report(self.y_test, y_pred))
        
        # 混淆矩阵
        cm = confusion_matrix(self.y_test, y_pred)
        plt.figure(figsize=(8, 6))
        sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
        plt.title('混淆矩阵')
        plt.xlabel('预测标签')
        plt.ylabel('真实标签')
        plt.savefig('confusion_matrix.png', dpi=300, bbox_inches='tight')
        plt.show()
        
        return accuracy
    
    def feature_importance_plot(self):
        """特征重要性可视化"""
        plt.figure(figsize=(10, 6))
        sns.barplot(data=self.feature_importance, x='importance', y='feature')
        plt.title('特征重要性')
        plt.xlabel('重要性')
        plt.tight_layout()
        plt.savefig('feature_importance.png', dpi=300, bbox_inches='tight')
        plt.show()

def main():
    """主函数"""
    print("🚢 泰坦尼克生存预测项目开始")
    
    # 创建预测器实例
    predictor = TitanicSurvivalPredictor()
    
    # 1. 加载数据
    if not predictor.load_data():
        print("数据加载失败，请检查网络连接")
        return
    
    # 2. 数据探索
    predictor.explore_data()
    
    # 3. 数据可视化
    predictor.visualize_data()
    
    # 4. 数据预处理
    predictor.preprocess_data()
    
    # 5. 模型训练
    predictor.train_model()
    
    # 6. 模型评估
    accuracy = predictor.evaluate_model()
    
    # 7. 特征重要性分析
    predictor.feature_importance_plot()
    
    print(f"\n🎉 项目完成！最终准确率: {accuracy:.3f}")
    print("\n📊 项目总结:")
    print("- 完成了完整的数据分析流程")
    print("- 实现了特征工程和模型训练")
    print("- 达到了可接受的预测准确率")
    print("- 生成了可视化分析图表")

if __name__ == "__main__":
    main()